#include <print.h>
#include <stdarg.h>


// 光标位置
static U16 CursorPosX;
static U16 CursorPosY;

// 字体颜色
static U08 FontColor;

// 用于数值 0 ~ 15 转成对应的字符 '0' ~ 'F'
static const U08 IntToStr[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

// 获取 eflags 状态寄存器的 IF 位
#define EFLAGS_IF(eflags)   ((eflags)&0x200)

/******************************************************************************
* 函数名称: E_RET SetCursorPos(U16 x, U16 y)
* 功能说明: 设置光标位置
* 输入参数: U16 x		--横坐标
    　　　　U16 y       --纵坐标
* 输出参数: 无
* 函数返回: E_OK:成功; E_ERR:失败
* 其它说明: 无
******************************************************************************/
E_RET SetCursorPos(U16 x, U16 y)
{
    U16 bx_c = 0;
    U32 eflags_c = 0; 

    // 对传入的坐标进行合法性检查
    if(x >= SCREEN_WIDTH || y >= SCREEN_WIDTH)
        return E_ERR;

    CursorPosX = x;
    CursorPosY = y;

    // 光标位置须按照 bx = (SCREEN_WIDTH * y + x) 公式转换一下放入 bx 寄存器中
    bx_c = SCREEN_WIDTH * y + x;

    // 获取 eflags 寄存器的值放到 eflags_c 变量中
    asm volatile("pushf;popl %%eax":"=a"(eflags_c));

    // 关闭外部中断
    asm volatile("cli");

    asm volatile(
            // 设置光标高 8 位：先把数据 0x0E 写入端口 0x03D4，然后把 bh 写入端口 0x03D5
            "movw %0,      %%bx\n"
            "movw $0x03D4, %%dx\n"
            "movb $0x0E,   %%al\n"
            "outb %%al,    %%dx\n"
            "nop;nop;nop;nop;nop;"
            "movw $0x03D5, %%dx\n"
            "movb %%bh,    %%al\n"
            "outb %%al,    %%dx\n"
            "nop;nop;nop;nop;nop;"

            // 设置光标低 8 位：先把数据 0x0F 写入端口 0x03D4，然后把 bl 写入端口 0x03D5
            "movw $0x03D4, %%dx\n"
            "movb $0x0F,   %%al\n"
            "outb %%al,    %%dx\n"
            "nop;nop;nop;nop;nop;"
            "movw $0x03D5, %%dx\n"
            "movb %%bl,    %%al\n"
            "outb %%al,    %%dx\n"
            "nop;nop;nop;nop;nop;"
            :
            : "b"(bx_c)  // 相当于：bx = bx_c，等号左边是汇编中的寄存器 bx，等号右边是 C 代码中的变量
            : "ax", "dx" // 告诉 gcc 编译器，ax，dx 寄存器被内嵌汇编使用，需要 gcc 自动添加保护和恢复操作（入栈和出栈）
                         // 明明 ax, bx, dx 三个寄存器都被使用了，为什么这里却只有 ax 和 dx 寄存器？
                         // "b"(bx_c) 中使用约束名 "b" 时 gcc 就已经自动对 bx 进行保护和恢复操作，所以这里就不需要重复了
        );

    // 若 bit9(IF 位) 为 1，则开启外部中断
    if(EFLAGS_IF(eflags_c))
        asm volatile("sti");
        
    return E_OK;
}

/******************************************************************************
* 函数名称: void SetFontColor(E_FONT_COLOR color)
* 功能说明: 设置字体颜色
* 输入参数: E_FONT_COLOR color --字体颜色
* 输出参数: 无
* 函数返回: 无
* 其它说明: 无
******************************************************************************/
void SetFontColor(E_FONT_COLOR color)
{
    FontColor = color;
}

/******************************************************************************
* 函数名称: static void PrintChar(U08 ch)
* 功能说明: 打印一个字符
* 输入参数: U08 ch --要打印的字符
* 输出参数: 无
* 函数返回: 无
* 其它说明: 无
******************************************************************************/
static void PrintChar(U08 ch)
{
    U32 edi_c = (SCREEN_WIDTH * CursorPosY + CursorPosX) * 2;
    U08 ah_c = FontColor;
    U08 al_c = ch;

    // 每打印一个字符，光标都往后移动一个位置
    CursorPosX++;
    // 如果光标达到最大横坐标值了，则设置光标到下一行起始位置
    if(CursorPosX >= SCREEN_WIDTH)
    {
        CursorPosX = 0;
        CursorPosY++;
    }

    if('\n' == al_c || '\r' == al_c) // 遇到换行，则光标位置设置成下一行的起始位置处
    {
        CursorPosX = 0;
        CursorPosY++;
    }
    else
    {
        // 打印一个字符
        asm volatile(
                "movl %0,   %%edi\n"         // edi：显存中的偏移量
                "movb %1,   %%ah\n"          // 显示属性
                "movb %2,   %%al\n"          // 要打印的字符
                "movw %%ax, %%gs:(%%edi)"    // 把显示数据放入显存
                :
                : "D"(edi_c), "r"(ah_c), "r"(al_c) // %0 替换成 edi_c,"D" 表示使用 edi 寄存器; %1 替换成 ah_c; %2 替换成 al_c;
                : "ax"
                );
    }

    // 更新光标位置
    SetCursorPos(CursorPosX, CursorPosY);
}

/******************************************************************************
* 函数名称: static E_RET PrintString(U08 *str)
* 功能说明: 打印字符串
* 输入参数: U08 *str --要打印的字符串
* 输出参数: 无
* 函数返回: E_OK:成功; E_ERR:失败
* 其它说明: 无
******************************************************************************/
static E_RET PrintString(U08 const *str)
{
    // 检查 *str 是否为空
    if(NULL == str)
        return E_ERR;

    while(*str)
        PrintChar(*str++);

    return E_OK;
}

/******************************************************************************
* 函数名称: static void PrintIntDec(S32 num)
* 功能说明: 以十进制的方式打印一个整型数
* 输入参数: S32 num --要打印的整型数
* 输出参数: 无
* 函数返回: 无
* 其它说明: 无
******************************************************************************/
static void PrintIntDec(S32 num)
{
    // 如果 num 为负数，则先把负号 '-' 打印出来; num = -num: 取一个负数的绝对值
    if(num < 0)
    {
        PrintChar('-');
        num = -num;
    }

    if(num < 10)  // 递归出口条件
        PrintChar(IntToStr[num]);
    else
    {
        PrintIntDec(num / 10);
        PrintIntDec(num % 10);
    }
}

/******************************************************************************
* 函数名称: static void PrintIntHex(U32 num)
* 功能说明: 以十六进制的方式打印一个无符号整型数
* 输入参数: U32 num --要打印的无符号整型数
* 输出参数: 无
* 函数返回: 无
* 其它说明: 无
******************************************************************************/
static void PrintIntHex(U32 num)
{
    U08 print[11] = { 0 }; // 初始化时必须清零，最大占 12 字节，比如 "0xFFFFFFFF\0"
    U08 i = 0;
    U08 tmp = 0;

    print[0] = '0';
    print[1] = 'x';
    for(i = 9; i >= 2; i--)
    {
        tmp = num & 0xF;
        num = num >> 4;
        print[i] = IntToStr[tmp];
    }

    PrintString(print);
}

/******************************************************************************
* 函数名称: void ClearScreen(void)
* 功能说明: 清空屏幕
* 输入参数: 无
* 输出参数: 无
* 函数返回: 无
* 其它说明: 无
******************************************************************************/
void ClearScreen(void)
{
    U32 i = 0;
    U32 eflags_c = 0;

    // 获取 eflags 寄存器的值放到 eflags_c 变量中
    asm volatile("pushf;popl %%eax":"=a"(eflags_c));

    // 关闭外部中断
    asm volatile("cli");

    SetCursorPos(0, 0);
    for(i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++)
        PrintChar(' ');
    SetCursorPos(0, 0);

    // 若 bit9(IF 位) 为 1，则开启外部中断
    if(EFLAGS_IF(eflags_c))
        asm volatile("sti");
}

/******************************************************************************
* 函数名称: E_RET printk(const char * fmt, ...)
* 功能说明: 通用打印
* 输入参数: const char * fmt    --输出格式, 目前支持的格式: 
                                        %c: 字符
                                        %s: 字符串 
                                        %x: 十六进制 
                                        %d: 十进制
    　　　　...                 --可变参数  
* 输出参数: 无
* 函数返回: E_OK:成功; E_ERR:失败
* 其它说明: 无
******************************************************************************/
E_RET printk(const char * fmt, ...)
{
    va_list args;
    U32 eflags_c = 0;
    
    va_start(args, fmt);

    // 获取 eflags 寄存器的值放到 eflags_c 变量中
    asm volatile("pushf;popl %%eax":"=a"(eflags_c));

    // 关闭外部中断
    asm volatile("cli");

    // 遍历字符串 fmt
    for(; *fmt; ++fmt)
    {
        // 寻找格式转换字符 '%', 如果不是 '%', 则直接打印出来
        if(*fmt != '%')
        {
            PrintChar(*fmt);
            continue;
        }

        // 如果找到了 '%', 则取 '%' 后的第一个字符
        ++fmt;             
        switch (*fmt)           // 根据 '%' 后的字符类型采取不同的打印接口
        {
            case 'c':           // 字符
                PrintChar((U08)va_arg(args, U32));
                break;

            case 's':           // 字符串
                PrintString(va_arg(args, U08 *));
                break;

            case 'x':           // 十六进制     
                PrintIntHex(va_arg(args, U32));
                break;
            
            case 'd':           // 十进制   
                PrintIntDec(va_arg(args, U32)); 
                break;
            
            default:
                return E_ERR;
                break;
        }

    }

    // 若 bit9(IF 位) 为 1，则开启外部中断
    if(EFLAGS_IF(eflags_c))
        asm volatile("sti");

    va_end(args);

    return E_OK;
}
